在前面的篇章中,我們深入理解了 Rust 的安全保證。
今天我們要面對一個現實:有時候,我們必須繞過編譯器的檢查。
關鍵問題是:如何在必要時使用 unsafe,同時將風險控制在最小範圍?
// unsafe 是一個承諾:
// "我保證這段程式碼遵守 Rust 的安全規則,
// 即使編譯器無法驗證"
unsafe {
// 編譯器信任你
// 但責任在你身上
}
關鍵洞察:unsafe
不是關閉安全檢查,而是將檢查的責任從編譯器轉移到程式設計師。
// 只有五件事需要 unsafe:
// 1. 解引用裸指標
unsafe {
let ptr = 0x12345678 as *const i32;
let value = *ptr; // 可能指向無效記憶體
}
// 2. 呼叫 unsafe 函式或方法
unsafe {
libc::malloc(1024);
}
// 3. 存取或修改可變靜態變數
static mut COUNTER: i32 = 0;
unsafe {
COUNTER += 1;
}
// 4. 實作 unsafe trait
unsafe trait UnsafeTrait {}
unsafe impl UnsafeTrait for MyType {}
// 5. 存取 union 的欄位
union MyUnion {
i: i32,
f: f32,
}
let u = MyUnion { i: 42 };
unsafe {
let f = u.f;
}
// unsafe 不會關閉借用檢查
unsafe {
let mut x = 5;
let r1 = &x;
let r2 = &mut x; // ❌ 仍然是錯誤
}
// unsafe 不會關閉生命週期檢查
unsafe {
let r;
{
let x = 5;
r = &x; // ❌ 仍然是錯誤
}
}
// 糟糕:整個函式都是 unsafe
unsafe fn bad_function(ptr: *const i32, len: usize) -> Vec<i32> {
let mut result = Vec::new();
for i in 0..len {
result.push(*ptr.add(i)); // 只有這行需要 unsafe
}
result
}
// 呼叫者也必須寫 unsafe
fn caller() {
let data = vec![1, 2, 3];
unsafe {
let result = bad_function(data.as_ptr(), data.len());
}
}
// 好的:只有必要的部分是 unsafe
fn good_function(ptr: *const i32, len: usize) -> Vec<i32> {
let mut result = Vec::new();
for i in 0..len {
unsafe {
result.push(*ptr.add(i)); // 只有這行是 unsafe
}
}
result
}
// 更好的:提供安全的包裝
fn best_function(slice: &[i32]) -> Vec<i32> {
// 完全安全的介面
slice.to_vec()
}
// 不安全的內部實作
unsafe fn raw_read(ptr: *const u8, len: usize) -> Vec<u8> {
let mut buffer = Vec::with_capacity(len);
std::ptr::copy_nonoverlapping(ptr, buffer.as_mut_ptr(), len);
buffer.set_len(len);
buffer
}
// 安全的公開介面
pub fn safe_read(ptr: *const u8, len: usize) -> Result<Vec<u8>, String> {
// 驗證前置條件
if ptr.is_null() {
return Err("指標不能為 null".to_string());
}
if len == 0 {
return Ok(Vec::new());
}
// 在驗證後才呼叫 unsafe
unsafe {
Ok(raw_read(ptr, len))
}
}
use std::ptr;
// 不安全的 C 資源
extern "C" {
fn create_resource() -> *mut Resource;
fn destroy_resource(ptr: *mut Resource);
fn use_resource(ptr: *mut Resource) -> i32;
}
// 安全的 RAII 包裝
pub struct SafeResource {
ptr: *mut Resource,
}
impl SafeResource {
pub fn new() -> Result<Self, String> {
unsafe {
let ptr = create_resource();
if ptr.is_null() {
Err("建立資源失敗".to_string())
} else {
Ok(SafeResource { ptr })
}
}
}
// 提供安全的方法
pub fn use_it(&self) -> Result<i32, String> {
unsafe {
let result = use_resource(self.ptr);
if result >= 0 {
Ok(result)
} else {
Err("操作失敗".to_string())
}
}
}
}
impl Drop for SafeResource {
fn drop(&mut self) {
unsafe {
destroy_resource(self.ptr);
}
}
}
// 使用者完全不需要寫 unsafe
fn user_code() -> Result<(), String> {
let resource = SafeResource::new()?;
let result = resource.use_it()?;
println!("結果: {}", result);
Ok(())
}
/// 一個安全的環形緩衝區
///
/// # 不變式
/// - `read_pos` 和 `write_pos` 永遠在 `0..capacity` 範圍內
/// - 緩衝區永遠不會超過容量
/// - 所有索引計算都經過邊界檢查
pub struct RingBuffer<T> {
buffer: Vec<T>,
read_pos: usize,
write_pos: usize,
capacity: usize,
}
impl<T> RingBuffer<T> {
pub fn new(capacity: usize) -> Self {
assert!(capacity > 0, "容量必須大於 0");
RingBuffer {
buffer: Vec::with_capacity(capacity),
read_pos: 0,
write_pos: 0,
capacity,
}
}
pub fn push(&mut self, value: T) -> Result<(), T> {
if self.is_full() {
return Err(value);
}
// 安全:我們已經檢查過容量
unsafe {
let ptr = self.buffer.as_mut_ptr().add(self.write_pos);
ptr.write(value);
}
// 維護不變式
self.write_pos = (self.write_pos + 1) % self.capacity;
Ok(())
}
fn is_full(&self) -> bool {
(self.write_pos + 1) % self.capacity == self.read_pos
}
}
use std::alloc::{alloc, dealloc, Layout};
use std::ptr;
pub struct MyVec<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> MyVec<T> {
pub fn new() -> Self {
MyVec {
ptr: ptr::null_mut(),
len: 0,
capacity: 0,
}
}
pub fn push(&mut self, value: T) {
if self.len == self.capacity {
self.grow();
}
unsafe {
// 安全:我們確保有足夠容量
let ptr = self.ptr.add(self.len);
ptr.write(value);
}
self.len += 1;
}
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
unsafe {
// 安全:len 現在指向最後一個元素
Some(ptr::read(self.ptr.add(self.len)))
}
}
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
return None;
}
unsafe {
// 安全:我們已經檢查過邊界
Some(&*self.ptr.add(index))
}
}
fn grow(&mut self) {
let new_capacity = if self.capacity == 0 {
1
} else {
self.capacity * 2
};
let new_layout = Layout::array::<T>(new_capacity).unwrap();
let new_ptr = if self.capacity == 0 {
unsafe { alloc(new_layout) as *mut T }
} else {
let old_layout = Layout::array::<T>(self.capacity).unwrap();
unsafe {
let ptr = alloc(new_layout) as *mut T;
ptr::copy_nonoverlapping(self.ptr, ptr, self.len);
dealloc(self.ptr as *mut u8, old_layout);
ptr
}
};
self.ptr = new_ptr;
self.capacity = new_capacity;
}
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
if self.capacity == 0 {
return;
}
// 呼叫所有元素的 Drop
while let Some(_) = self.pop() {}
// 釋放記憶體
let layout = Layout::array::<T>(self.capacity).unwrap();
unsafe {
dealloc(self.ptr as *mut u8, layout);
}
}
}
// 使用者完全不需要寫 unsafe
fn use_my_vec() {
let mut vec = MyVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
assert_eq!(vec.get(1), Some(&2));
assert_eq!(vec.pop(), Some(3));
}
// Send 和 Sync 是 unsafe trait
unsafe trait Send {}
unsafe trait Sync {}
// 為什麼是 unsafe?
// 因為實作者必須保證執行緒安全
// 編譯器無法自動驗證
// 自定義 unsafe trait
unsafe trait TrustedLen: Iterator {
// 承諾:size_hint() 回傳的上界是精確的
}
// 實作時必須寫 unsafe
unsafe impl<T> TrustedLen for std::vec::IntoIter<T> {}
use std::marker::PhantomData;
// 預設情況:包含裸指標的型別不是 Send
struct NotSend {
ptr: *mut i32,
}
// 如果我們確定這是安全的,可以手動實作
struct IsSend {
ptr: *mut i32,
_marker: PhantomData<i32>, // 標記所有權
}
unsafe impl Send for IsSend {}
// 承諾:這個型別可以安全地在執行緒間傳遞
// 使用時的責任
fn use_send() {
let data = Box::new(42);
let ptr = Box::into_raw(data);
let wrapper = IsSend {
ptr,
_marker: PhantomData,
};
// 可以傳遞到其他執行緒
std::thread::spawn(move || {
unsafe {
println!("{}", *wrapper.ptr);
// 必須確保正確釋放
let _ = Box::from_raw(wrapper.ptr);
}
});
}
// 問自己:
// - 有沒有安全的替代方案?
// - 能不能用標準庫的安全抽象?
// - 效能提升是否值得增加的風險?
// 通常不需要 unsafe
fn safe_version(slice: &[i32]) -> i32 {
slice.iter().sum()
}
// 只在效能關鍵路徑才考慮 unsafe
fn unsafe_version(ptr: *const i32, len: usize) -> i32 {
let mut sum = 0;
for i in 0..len {
unsafe {
sum += *ptr.add(i);
}
}
sum
}
// 糟糕:沒有驗證
unsafe fn bad(ptr: *const i32) -> i32 {
*ptr // 如果 ptr 是 null 怎麼辦?
}
// 好的:驗證前置條件
fn good(ptr: *const i32) -> Result<i32, String> {
if ptr.is_null() {
return Err("指標不能為 null".to_string());
}
unsafe {
Ok(*ptr)
}
}
// 文件化所有不變式
/// # 不變式
/// - `len <= capacity`
/// - `ptr` 指向至少 `capacity` 個 T 的有效記憶體
/// - 前 `len` 個元素已初始化
struct Container<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> Container<T> {
// 每個方法都必須維護不變式
fn push(&mut self, value: T) {
assert!(self.len <= self.capacity); // 檢查不變式
if self.len == self.capacity {
self.grow();
}
unsafe {
self.ptr.add(self.len).write(value);
}
self.len += 1;
assert!(self.len <= self.capacity); // 確保不變式
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unsafe_function() {
// 測試正常情況
let data = vec![1, 2, 3];
let result = unsafe_function(data.as_ptr(), data.len());
assert_eq!(result, 6);
}
#[test]
fn test_edge_cases() {
// 測試邊界情況
let empty: Vec<i32> = vec![];
let result = unsafe_function(empty.as_ptr(), 0);
assert_eq!(result, 0);
}
#[test]
#[should_panic]
fn test_invalid_input() {
// 測試無效輸入
unsafe_function(std::ptr::null(), 10);
}
}
// 只在必要的地方使用 unsafe
fn minimal_unsafe() {
let safe_part = prepare_data();
unsafe {
// 只有這一行是 unsafe
dangerous_operation();
}
process_result();
}
// 內部 unsafe,外部安全
pub struct SafeWrapper {
inner: UnsafeInner,
}
impl SafeWrapper {
pub fn safe_method(&self) {
// 內部處理 unsafe
}
}
/// # Safety
///
/// 呼叫者必須確保:
/// - `ptr` 指向至少 `len` 個有效的 T
/// - `ptr` 在呼叫期間保持有效
/// - 沒有其他執行緒同時存取這塊記憶體
pub unsafe fn documented_unsafe(ptr: *const T, len: usize) {
// 實作
}
#[cfg(test)]
mod tests {
// 測試所有邊界情況
// 使用 miri 檢測未定義行為
// 使用 sanitizers 檢測記憶體錯誤
}
關鍵洞察:unsafe
不是逃生門,而是一個需要極度謹慎的工具。好的 unsafe 程式碼應該將風險完全封裝在內部,對外提供完全安全的介面。
在下一篇中,我們將探討 診斷與記憶體檢查,看看如何系統性地驗證我們的假設。